﻿#include <iostream>

#include "Scene/Mesh.h"
#include "Scene/Resource_manager.h"

//#define STB_IMAGE_IMPLEMENTATION
//#include <stb_image/stb_image.h>

namespace CC
{

   // static  AnimationInfo g_AnimationInfo;
   // static std::vector<Mesh> m_Meshes;


    Mesh::Mesh(std::string name, std::vector<Vertex> vertices,
               std::vector<unsigned int> indices,
               std::vector<Texture2D*> textures)
    {
        Name = name;
        Vertices = vertices;
        Indices = indices;
        Textures = textures;
        HasColorMaterial = false;
    }

    Mesh::Mesh(std::string name, std::vector<Vertex> vertices,
        std::vector<unsigned int> indices,
        std::vector<StdImgInfo> texturedatas)
    {
        Name = name;
        Vertices = vertices;
        Indices = indices;
        TexturesData = texturedatas;
        HasColorMaterial = false;
    }

    Mesh::Mesh(std::string name, std::vector<Vertex> vertices, std::vector<unsigned int> indices, MaterialMesh colorMaterial)
    {
        Name = name;
        Vertices = vertices;
        Indices = indices;
        ColorMaterial = colorMaterial;
        HasColorMaterial = true;
    }

    Mesh::~Mesh()
    {
        if(TriangleVertexBufferBase)
            delete[] TriangleVertexBufferBase;
        if(TriangleVertexArray)
            delete TriangleVertexArray;
        Vertices.clear();
        Indices.clear();
        Textures.clear();
    }

    void Mesh::SetupMesh()
    {
        size_t vertexCount = Vertices.size();
        size_t texCount = Textures.size();
        size_t idCount = Indices.size();

        TriangleIndexCount = idCount;

        TriangleVertexArray = VertexArray::Create();
        VertexBuffer *TriangleVertexBuffer = VertexBuffer::Create(vertexCount * sizeof(TriangleVertex));
        TriangleVertexBuffer->SetLayout({ {ShaderDataType::Float3, "a_Position"},
                                                {ShaderDataType::Float4, "a_Color"},
                                                {ShaderDataType::Float3, "a_Normal"},
                                                {ShaderDataType::Float2, "a_TexCoord"},
                                                {ShaderDataType::Float3, "a_Tangent"},
                                                {ShaderDataType::Float3, "a_Bitangent"},
                                                {ShaderDataType::Int4, "a_BoneIds"},
                                                {ShaderDataType::Float4, "a_Weights"},
                                                {ShaderDataType::Float, "a_TexIndex"},
                                                {ShaderDataType::Float, "a_TilingFactor"},
                                                {ShaderDataType::Int, "a_EntityID"} });
        TriangleVertexArray->AddVertexBuffer(TriangleVertexBuffer);

        TriangleVertexBufferBase = new TriangleVertex[vertexCount];
        TriangleVertex* TriangleVertexBufferPtr = TriangleVertexBufferBase;

        CC_CORE_INFO("SetupMesh new Triangle3d[1]\n");
        IndexBuffer *triangleIB = IndexBuffer::Create(Indices.size() * sizeof(uint32_t));
        TriangleVertexArray->SetIndexBuffer(triangleIB);
        triangleIB->SetData((const void *)(Indices.data()), (uint32_t)(idCount * sizeof(unsigned int)));


        float textureIndex = 100.0f;

        for (size_t i = 0; i < texCount; i++)
        {
            Texture2D *t = Textures[i];
            std::string name = t->TypeName;
            if (name == "texture_diffuse") {
                textureIndex = 0.0f;
                break;
            }
            //if (name == "texture_specular")
            //    textureIndex += 1.0f;
            //if (name == "texture_normal")
            //    textureIndex += 1.0f;
            //if (name == "texture_height")
            //    textureIndex += 1.0f;
            //
            //if (name == "texture_shininess_roughness") {
            //    textureIndex = 5.0f;
            //    break;
            //}
        }

        for (size_t i = 0; i < vertexCount; i++)
        {
            Vertex &v = Vertices[i];
            TriangleVertexBufferPtr->Position = v.Position;
            TriangleVertexBufferPtr->Color = glm::vec4(1.0);
            TriangleVertexBufferPtr->Normal = v.Normal;
            TriangleVertexBufferPtr->TexCoord = v.TexCoords;
            TriangleVertexBufferPtr->Tangent = v.Tangent;
            TriangleVertexBufferPtr->Bitangent = v.Bitangent;
            TriangleVertexBufferPtr->BoneIDs[0] = v.m_BoneIDs[0];
            TriangleVertexBufferPtr->BoneIDs[1] = v.m_BoneIDs[1];
            TriangleVertexBufferPtr->BoneIDs[2] = v.m_BoneIDs[2];
            TriangleVertexBufferPtr->BoneIDs[3] = v.m_BoneIDs[3];
            TriangleVertexBufferPtr->Weights[0] = v.m_Weights[0];
            TriangleVertexBufferPtr->Weights[1] = v.m_Weights[1];
            TriangleVertexBufferPtr->Weights[2] = v.m_Weights[2];
            TriangleVertexBufferPtr->Weights[3] = v.m_Weights[3];
            TriangleVertexBufferPtr->TexIndex = textureIndex;
            TriangleVertexBufferPtr->TilingFactor = 1.0;// TilingFactor;
            TriangleVertexBufferPtr->EntityID = 0;
            TriangleVertexBufferPtr++;

        }

        uint32_t dataSize = (uint32_t)((uint8_t *)TriangleVertexBufferPtr - (uint8_t *)TriangleVertexBufferBase);
        TriangleVertexBuffer->SetData(TriangleVertexBufferBase, dataSize);

    }

    void Mesh::SetupMeshInstance(const Mesh* mesh)
    {
        if (!mesh) {
            SetupMesh();
            return;
        }
        size_t vertexCount = mesh->Vertices.size();
        size_t texCount = mesh->Textures.size();
        size_t idCount = mesh->Indices.size();

        TriangleIndexCount = idCount;

        TriangleVertexArray = mesh->TriangleVertexArray;

        TriangleVertexBufferBase = mesh->TriangleVertexBufferBase;
    }

    std::vector<Mesh> MeshHelper::MeshCreate(const std::string &path, const std::string &name, AnimationInfo &a, bool sync, TextureType textureType)
    {
        m_Meshes.clear();
        g_AnimationInfo.Clear();

        LoadModelFromFile(path, sync, textureType);
        a = g_AnimationInfo;
        return m_Meshes;
    }

    void MeshHelper::LoadModelFromFile(const std::string &path, bool sync, TextureType textureType)
    {
        const aiScene *scene = aiImportFile(path.c_str(),
                                            aiProcess_Triangulate | aiProcess_FlipUVs | aiProcess_CalcTangentSpace);

        // If the import failed, report it
        if (!scene || scene->mFlags & AI_SCENE_FLAGS_INCOMPLETE || !scene->mRootNode)
        {
            CC_CORE_WARN("ERROR::ASSIMP:: %s\n", aiGetErrorString());
            return;
        }

        int index = path.find_last_of('/');
        if (index == -1) {
            index = path.find_last_of("\\");
        }
        g_AnimationInfo.Directory = path.substr(0, index);
  
        // Now we can access the file's contents.
        ProcessNode(scene->mRootNode, scene, sync, textureType);
    }

    void MeshHelper::ProcessNode(aiNode *node, const aiScene *scene, bool sync, TextureType textureType)
    {
        unsigned int i = 0;
        for (i = 0; i < node->mNumMeshes; ++i)
        {
            C_STRUCT aiMesh *mesh = scene->mMeshes[node->mMeshes[i]];
            m_Meshes.push_back(ProcessMesh(mesh, scene, sync, textureType));
        }

        for (i = 0; i < node->mNumChildren; ++i)
        {
            ProcessNode(node->mChildren[i], scene, sync, textureType);
        }
    }

    Mesh MeshHelper::ProcessMesh(aiMesh *mesh, const aiScene *scene, bool sync, TextureType textureType)
    {
        std::vector<Vertex> vertices;
        std::vector<unsigned int> indices;
        std::vector<Texture2D*> textures;
        std::vector<StdImgInfo> texturedatas;

        //     printf("mesh->mNumVertices=%d;mNumFaces=%d\n", mesh->mNumVertices, mesh->mNumFaces);
        for (unsigned int i = 0; i < mesh->mNumVertices; i++)
        {
            Vertex vertex;
            SetVertexBoneDataToDefault(vertex);
            vertex.Position = AssimpGLMHelpers::GetGLMVec(mesh->mVertices[i]);

            if (mesh->HasNormals())
            {
                vertex.Normal = AssimpGLMHelpers::GetGLMVec(mesh->mNormals[i]);
            }
            // texture coordinates
            if (mesh->mTextureCoords[0]) // does the mesh contain texture coordinates?
            {
                glm::vec2 vec;
                // a vertex can contain up to 8 different texture coordinates. We thus make the assumption that we won't
                // use models where a vertex can have multiple texture coordinates so we always take the first set (0).
                vec.x = mesh->mTextureCoords[0][i].x;
                vec.y = mesh->mTextureCoords[0][i].y;
                vertex.TexCoords = vec;
                // tangent
                if (mesh->mTangents)
                {
                    vertex.Tangent = AssimpGLMHelpers::GetGLMVec(mesh->mTangents[i]);
                }

                // bitangent
                if (mesh->mBitangents)
                {
                    vertex.Bitangent = AssimpGLMHelpers::GetGLMVec(mesh->mBitangents[i]);
                }
            }
            else
                vertex.TexCoords = glm::vec2(0.0f, 0.0f);

            vertices.push_back(vertex);
        }

        // now wak through each of the mesh's faces (a face is a mesh its triangle) and retrieve the corresponding vertex indices.
        for (unsigned int i = 0; i < mesh->mNumFaces; i++)
        {
            aiFace face = mesh->mFaces[i];
            // retrieve all indices of the face and store them in the indices vector
            for (unsigned int j = 0; j < face.mNumIndices; j++)
                indices.push_back(face.mIndices[j]);
        }
        // for(int i=0;i<indices.size();i++){
        //     printf("%d:%d\n", i, indices[i]);
        // }

        MaterialMesh colorMaterial;
        bool hasColorMaterial = false;
        if (mesh->mMaterialIndex >= 0)
        {
            aiMaterial *material = scene->mMaterials[mesh->mMaterialIndex];
            /*   for(int t=0;t<0xC;t++){
                   CC_CORE_INFO("%d: %d \n", t, material->GetTextureCount((aiTextureType)t));
               }*/

            if (sync) {
                std::vector<StdImgInfo> diffuseMaps = LoadMaterialTexturesSync(material, aiTextureType_DIFFUSE, "texture_diffuse", textureType);
                texturedatas.insert(texturedatas.end(), diffuseMaps.begin(), diffuseMaps.end());
                // 2. specular maps |material
                std::vector<StdImgInfo> specularMaps = LoadMaterialTexturesSync(material, aiTextureType_SPECULAR, "texture_specular", textureType);
                texturedatas.insert(texturedatas.end(), specularMaps.begin(), specularMaps.end());
                // 3. normal maps
                std::vector<StdImgInfo> normalMaps = LoadMaterialTexturesSync(material, aiTextureType_NORMALS, "texture_normal", textureType);
                texturedatas.insert(texturedatas.end(), normalMaps.begin(), normalMaps.end());
                // 4. height maps|normal
                std::vector<StdImgInfo> heightMaps = LoadMaterialTexturesSync(material, aiTextureType_HEIGHT, "texture_height", textureType);
                texturedatas.insert(texturedatas.end(), heightMaps.begin(), heightMaps.end());
                // 4. AMBIENT maps
                std::vector<StdImgInfo> ambientMaps = LoadMaterialTexturesSync(material, aiTextureType_AMBIENT, "texture_ambient", textureType);
                texturedatas.insert(texturedatas.end(), ambientMaps.begin(), ambientMaps.end());
                // 5. LIGHTMAP maps AO
                std::vector<StdImgInfo> lightmapMaps = LoadMaterialTexturesSync(material, aiTextureType_LIGHTMAP, "texture_lightmap", textureType);
                texturedatas.insert(texturedatas.end(), lightmapMaps.begin(), lightmapMaps.end());

                std::vector<StdImgInfo> shininess_roughnessMaps = LoadMaterialTexturesSync(material, aiTextureType_SHININESS, "texture_shininess_roughness", textureType);
                texturedatas.insert(texturedatas.end(), shininess_roughnessMaps.begin(), shininess_roughnessMaps.end());

                std::vector<StdImgInfo> opacity_aoMaps = LoadMaterialTexturesSync(material, aiTextureType_OPACITY, "texture_opacity_ao", textureType);//AO
                texturedatas.insert(texturedatas.end(), opacity_aoMaps.begin(), opacity_aoMaps.end());

            }
            else {
                std::vector<Texture2D*> diffuseMaps = LoadMaterialTextures(material, aiTextureType_DIFFUSE, "texture_diffuse", textureType);
                textures.insert(textures.end(), diffuseMaps.begin(), diffuseMaps.end());
                // 2. specular maps |material
                std::vector<Texture2D*> specularMaps = LoadMaterialTextures(material, aiTextureType_SPECULAR, "texture_specular", textureType);
                textures.insert(textures.end(), specularMaps.begin(), specularMaps.end());
                // 3. normal maps
                std::vector<Texture2D*> normalMaps = LoadMaterialTextures(material, aiTextureType_NORMALS, "texture_normal", textureType);
                textures.insert(textures.end(), normalMaps.begin(), normalMaps.end());
                // 4. height maps|normal
                std::vector<Texture2D*> heightMaps = LoadMaterialTextures(material, aiTextureType_HEIGHT, "texture_height", textureType);
                textures.insert(textures.end(), heightMaps.begin(), heightMaps.end());
                // 4. AMBIENT maps
                std::vector<Texture2D*> ambientMaps = LoadMaterialTextures(material, aiTextureType_AMBIENT, "texture_ambient", textureType);
                textures.insert(textures.end(), ambientMaps.begin(), ambientMaps.end());
                // 5. LIGHTMAP maps AO
                std::vector<Texture2D*> lightmapMaps = LoadMaterialTextures(material, aiTextureType_LIGHTMAP, "texture_lightmap", textureType);
                textures.insert(textures.end(), lightmapMaps.begin(), lightmapMaps.end());

                std::vector<Texture2D*> shininess_roughnessMaps = LoadMaterialTextures(material, aiTextureType_SHININESS, "texture_shininess_roughness", textureType);
                textures.insert(textures.end(), shininess_roughnessMaps.begin(), shininess_roughnessMaps.end());

                std::vector<Texture2D*> opacity_aoMaps = LoadMaterialTextures(material, aiTextureType_OPACITY, "texture_opacity_ao", textureType);//AO
                textures.insert(textures.end(), opacity_aoMaps.begin(), opacity_aoMaps.end());

            }
            
            if (material->GetTextureCount(aiTextureType_DIFFUSE) == 0 && material->GetTextureCount(aiTextureType_SPECULAR) == 0)
            {
                colorMaterial = LoadMaterialWithoutTextures(material);
                hasColorMaterial = true;
            }
        }

        ExtractBoneWeightForVertices(vertices, mesh, scene);

        if (hasColorMaterial)
        {
            return Mesh(std::string(mesh->mName.C_Str()), vertices, indices, colorMaterial);
        }
        else
        {
            if (sync) {
                return Mesh(std::string(mesh->mName.C_Str()), vertices, indices, texturedatas);
            }
            else {
                return Mesh(std::string(mesh->mName.C_Str()), vertices, indices, textures);
            }            
        }
    }

    void MeshHelper::SetVertexBoneDataToDefault(Vertex &vertex)
    {
        for (int i = 0; i < MAX_BONE_INFLUENCE; i++)
        {
            vertex.m_BoneIDs[i] = -1;
            vertex.m_Weights[i] = 0.0f;
        }
    }

    std::vector<Texture2D*> MeshHelper::LoadMaterialTextures(aiMaterial *mat, aiTextureType type, std::string typeName, TextureType textureType)
    {
        //CC_CORE_INFO("LoadMaterialTextures\n");
        std::vector<Texture2D*> textures;
        for (unsigned int i = 0; i < mat->GetTextureCount(type); i++)
        {
            aiString str;
            mat->GetTexture(type, i, &str);

            {
                std::string strlinux = std::string(str.C_Str());
                CC_CORE_INFO("LoadMaterialTextures type=%d %s;typeName %s\n", type, strlinux.c_str(), typeName.c_str());
                // if(int i=strlinux.find("\\") != std::string::npos){
                //  printf("~%s:%d typeName=%s, %s\n", strlinux.c_str(), i, typeName.c_str(), str.C_Str());
                //     strlinux = strlinux.replace(i, 1, "/");
                // }
                // std::replace(strlinux.begin(), strlinux.end(), "\\", "/");

                //Texture2D* t = Texture2D::Create((g_AnimationInfo.Directory + "/" + strlinux).c_str());
                Texture2D* t = ResourceManager::LoadTexture((g_AnimationInfo.Directory + "/" + strlinux).c_str(), strlinux, textureType);
                if (t) {
                    t->TypeName = typeName;
                    textures.push_back(t);
                }
                
            }
        }
        return textures;
    }

    std::vector<StdImgInfo> MeshHelper::LoadMaterialTexturesSync(aiMaterial *mat, aiTextureType type, std::string typeName, TextureType textureType)
    {
        //CC_CORE_INFO("LoadMaterialTextures\n");
        std::vector<StdImgInfo> textures;
        for (unsigned int i = 0; i < mat->GetTextureCount(type); i++)
        {
            aiString str;
            mat->GetTexture(type, i, &str);

            {
                std::string strlinux = std::string(str.C_Str());
                CC_CORE_INFO("LoadMaterialTexturesSync type=%d %s;typeName %s\n", type, strlinux.c_str(), typeName.c_str());

                int width, height, channels;
                unsigned char *data = nullptr;
                {
                    // HZ_PROFILE_SCOPE("stbi_load - OpenGLTexture2D::OpenGLTexture2D(const std::string&)");
                    data = ResourceManager::LoadStbFunction((g_AnimationInfo.Directory + "/" + strlinux).c_str(), width, height, channels, 0);
                }

                if (data) {
                    StdImgInfo info;
                    info.file = g_AnimationInfo.Directory + "/" + strlinux;
                    info.typeName = typeName;
                    info.imgData = data;
                    info.width = width;
                    info.height = height;
                    info.channels = channels;
                    info.textureType = textureType;
                    textures.push_back(info);
                }
            }
        }
        return textures;
    }

    MaterialMesh MeshHelper::LoadMaterialWithoutTextures(aiMaterial *mat)
    {
        MaterialMesh result;
        aiColor3D color;
        mat->Get(AI_MATKEY_COLOR_AMBIENT, color);
        result.Ambient = glm::vec4(color.r, color.g, color.b, 1.0);
        mat->Get(AI_MATKEY_COLOR_DIFFUSE, color);
        result.Diffuse = glm::vec4(color.r, color.g, color.b, 1.0);
        // result.Ambient = result.Diffuse * 0.1;

        mat->Get(AI_MATKEY_COLOR_SPECULAR, color);
        result.Specular = glm::vec4(color.r, color.g, color.b, 1.0);
        result.Shininess = 1.0;

        return result;
    }

    void MeshHelper::ExtractBoneWeightForVertices(std::vector<Vertex> &vertices, aiMesh *mesh, const aiScene *scene)
    {
        auto &boneInfoMap = g_AnimationInfo.BoneInfoMap;
        int &boneCount = g_AnimationInfo.BoneCounter;

        //     printf("mesh->mNumBones=%d\n", mesh->mNumBones);
        for (int boneIndex = 0; boneIndex < (int)mesh->mNumBones; ++boneIndex)
        {
            int boneID = -1;
            std::string boneName = mesh->mBones[boneIndex]->mName.C_Str();

            if (boneInfoMap.find(boneName) == boneInfoMap.end())
            {
                BoneInfo newBoneInfo;
                newBoneInfo.id = boneCount;
                newBoneInfo.offset = AssimpGLMHelpers::ConvertMatrixToGLMFormat(mesh->mBones[boneIndex]->mOffsetMatrix);
                boneInfoMap[boneName] = newBoneInfo;
                boneID = boneCount;
                boneCount++;
            }
            else
            {
                boneID = boneInfoMap[boneName].id;
            }
            assert(boneID != -1);
            auto weights = mesh->mBones[boneIndex]->mWeights;
            int numWeights = mesh->mBones[boneIndex]->mNumWeights;

            // printf("numWeights=%d\n", numWeights);

            for (int weightIndex = 0; weightIndex < numWeights; ++weightIndex)
            {
                int vertexId = weights[weightIndex].mVertexId;
                float weight = weights[weightIndex].mWeight;
                // printf("n````````````vertexId=%d %d %d %f\n", vertexId, vertices.size(), boneID, weight);
                assert(vertexId <= vertices.size());
                SetVertexBoneData(vertices[vertexId], boneID, weight);
                // printf("%f,%f,%f,%f\n", vertices[vertexId].m_Weights[0], vertices[vertexId].m_Weights[1],
                //     vertices[vertexId].m_Weights[2], vertices[vertexId].m_Weights[3]);
            }
        }

        // printf("model.getBoneCount()=%d\n", model.getBoneCount());
    }

    void MeshHelper::SetVertexBoneData(Vertex &vertex, int boneID, float weight)
    {
        for (int i = 0; i < MAX_BONE_INFLUENCE; ++i)
        {
            if (vertex.m_Weights[i] == 0.0)
            {
                vertex.m_Weights[i] = weight;
                vertex.m_BoneIDs[i] = boneID;
                break;
            }
        }
    }

    std::string Mesh::SerializeVertex() {
        std::string data;
        int len = Vertices.size();
        for (int i = 0; i < len; i++) {
            CC_CORE_INFO("Mesh SerializeVertex %d: %f %f %f\n", i, Vertices[i].Position[0], Vertices[i].Position[1], Vertices[i].Position[2]);
            data += Vertices[i].Serialize() + ":";
        }
        return data;
    }

    std::string Mesh::SerializeIndices() {
        std::string data;
        int len = Indices.size();
        for (int i = 0; i < len; i++) {
            data += Tool::UInt2Str(Indices[i]) + ",";
        }
        return data;
    }
    std::string Mesh::SerializeTextures() {
        std::string data;
        int len = Textures.size();
        for (int i = 0; i < len; i++) {
            data += Textures[i]->TypeName + "," + Textures[i]->GetPath() + ",";
        }
        return data;
    }

    void Mesh::ParseVertex(std::string data) {
        std::stringstream ss(data);
        std::string item;
        while (std::getline(ss, item, ':')) {
            if (!item.empty()) {
                Vertex v;
                v.Parse(item);
                Vertices.push_back(v);
            }
        }
    }
    void Mesh::ParseIndices(std::string data) {
        std::stringstream ss(data);
        std::string item;
        while (std::getline(ss, item, ',')) {
            if (!item.empty()) {
                unsigned int v;
                v = Tool::Str2UInt(item);
                Indices.push_back(v);
            }
        }
    }
    void Mesh::ParseTextures(std::string data) {

    }
   
}
